﻿using System;
using System.Collections.Generic;
using System.Text;
using UvsChess;

namespace StudentAI
{
    public static class Movement
    {
        #region Move generation functions

        private static HashSet<ChessLocation> getPawnMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();
            int ydir;
            int start_row;
            if (color == ChessColor.Black)
            {
                ydir = 1;
                start_row = 1;
            }
            else
            {
                ydir = -1;
                start_row = 6;
            }

            int newY = position.Y + ydir;
            
            //First check the bounds to make sure we are on the board
            if (newY >= 0 && newY < ChessBoard.NumberOfRows)
            {
                //Check forward move
                if (ChessPiece.Empty == board[position.X, newY])
                {
                    moves.Add(new ChessLocation(position.X, newY));

                    //Check double forward move,  if we are in the starting location and there is nobody two in front we can move there
                    if (position.Y == start_row && board[position.X, newY + ydir] == ChessPiece.Empty)
                    {
                        moves.Add(new ChessLocation(position.X, newY + ydir));
                    }
                }

                //Check diagonal left attack
                int attack = position.X - 1;
                if (attack >= 0 && isEnemy(board, color, new ChessLocation(attack, newY)))
                {
                    moves.Add(new ChessLocation(attack, newY));
                }

                //Check diagonal right attack
                attack = position.X + 1;
                if (attack < ChessBoard.NumberOfColumns && isEnemy(board, color, new ChessLocation(attack, newY)))
                {
                    moves.Add(new ChessLocation(attack, newY));
                }
            }

            return moves;
        }

        private static HashSet<ChessLocation> getRookMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();

            //Check in +y direction
            int y = position.Y + 1;

            //Look at all spaces from start
            while (y < ChessBoard.NumberOfRows)
            {
                ChessLocation loc = new ChessLocation(position.X, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    y++;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in the -y direction
            y = position.Y - 1;

            //Look at all spaces from start
            while (y >= 0)
            {
                ChessLocation loc = new ChessLocation(position.X, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    y--;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in +x direction
            int x = position.X + 1;

            //Look at all empty spaces from start
            while (x < ChessBoard.NumberOfColumns)
            {
                ChessLocation loc = new ChessLocation(x, position.Y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x++;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in the -x direction
            x = position.X - 1;
            
            //Look at all empty spaces from start
            while (x >= 0)
            {
                ChessLocation loc = new ChessLocation(x, position.Y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x--;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            return moves;
        }

        private static HashSet<ChessLocation> getBishopMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();

            //Check in +y +x direction
            int y = position.Y + 1;
            int x = position.X + 1;

            //Look at all empty spaces from start
            while (y < ChessBoard.NumberOfRows && x < ChessBoard.NumberOfColumns)
            {
                ChessLocation loc = new ChessLocation(x, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x++;
                    y++;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in +y -x direction
            y = position.Y + 1;
            x = position.X - 1;

            //Look at all empty spaces from start
            while (y < ChessBoard.NumberOfRows && x >= 0)
            {
                ChessLocation loc = new ChessLocation(x, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x--;
                    y++;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in -y -x direction
            y = position.Y - 1;
            x = position.X - 1;

            //Look at all empty spaces from start
            while (y >= 0 && x >= 0)
            {
                ChessLocation loc = new ChessLocation(x, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x--;
                    y--;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            //Check in -y +x direction
            y = position.Y - 1;
            x = position.X + 1;

            //Look at all empty spaces from start
            while (y >= 0 && x < ChessBoard.NumberOfColumns )
            {
                ChessLocation loc = new ChessLocation(x, y);

                if (board[loc] == ChessPiece.Empty)
                {
                    moves.Add(loc);
                    x++;
                    y--;
                }
                else if (isEnemy(board, color, loc))
                {
                    moves.Add(loc);
                    break;
                }
                else
                    break;
            }

            return moves;
        }

        private static HashSet<ChessLocation> getKnightMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();
	        foreach (ChessLocation move in new []{new ChessLocation(position.X + 2, position.Y + 1), new ChessLocation(position.X + 2, position.Y - 1),
                                                  new ChessLocation(position.X - 2, position.Y + 1), new ChessLocation(position.X - 2, position.Y - 1),
                                                  new ChessLocation(position.X + 1, position.Y + 2), new ChessLocation(position.X - 1, position.Y + 2),
                                                  new ChessLocation(position.X + 1, position.Y - 2), new ChessLocation(position.X - 1, position.Y - 2)})
	        {
                if ((move.Y < ChessBoard.NumberOfRows && move.Y >= 0 && move.X < ChessBoard.NumberOfColumns && move.X >= 0)
                    && (board[move] == ChessPiece.Empty || isEnemy(board, color, move)))
                {
                    moves.Add(move);
                }
	        }
	
	        return moves;
        }

        private static HashSet<ChessLocation> getQueenMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = getBishopMoves(board, color, position);
            moves.UnionWith(getRookMoves(board, color, position));
            return moves;
        }

        private static HashSet<ChessLocation> getKingMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();

            foreach (ChessLocation move in new[] {new ChessLocation(position.X + 1, position.Y + 1), new ChessLocation(position.X + 1, position.Y - 1),
                                                  new ChessLocation(position.X + 1, position.Y), new ChessLocation(position.X - 1, position.Y + 1),
                                                  new ChessLocation(position.X - 1, position.Y - 1), new ChessLocation(position.X - 1, position.Y),
                                                  new ChessLocation(position.X, position.Y + 1), new ChessLocation(position.X, position.Y - 1)})
            {
                if ((move.Y < ChessBoard.NumberOfRows && move.Y >= 0 && move.X < ChessBoard.NumberOfColumns && move.X >= 0)
                    && (board[move] == ChessPiece.Empty || isEnemy(board, color, move)))
                {
                    moves.Add(move);
                }
            }

            return moves;
        }

        private static HashSet<ChessLocation> getPieceMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            ChessPiece piece = board[position];
            HashSet<ChessLocation> moves = null;

            if (!isEnemy(board, color, position))
            {
                switch (piece)
                {
                    case ChessPiece.WhitePawn:
                    case ChessPiece.BlackPawn:
                        moves = getPawnMoves(board, color, position);
                        break;
                    case ChessPiece.WhiteRook:
                    case ChessPiece.BlackRook:
                        moves = getRookMoves(board, color, position);
                        break;
                    case ChessPiece.WhiteBishop:
                    case ChessPiece.BlackBishop:
                        moves = getBishopMoves(board, color, position);
                        break;
                    case ChessPiece.WhiteQueen:
                    case ChessPiece.BlackQueen:
                        moves = getQueenMoves(board, color, position);
                        break;
                    case ChessPiece.WhiteKnight:
                    case ChessPiece.BlackKnight:
                        moves = getKnightMoves(board, color, position);
                        break;
                    case ChessPiece.WhiteKing:
                    case ChessPiece.BlackKing:
                        moves = getKingMoves(board, color, position);
                        break;
                }
            }

            return moves;
        }

        public static HashSet<ChessLocation> getFilteredPieceMoves(ChessBoard board, ChessColor color, ChessLocation position)
        {
            return filterOutCheck(board, position, getPieceMoves(board, color, position), color);
        }

        /// <summary>
        /// Get all the moves for one color from a certain board configuration
        /// </summary>
        /// <param name="board">The board configuration</param>
        /// <param name="color">The color of pieces to get moves for</param>
        /// <returns>A list of the color's moves</returns>
        public static List<ChessMove> getMoves(ChessBoard board, ChessColor color)
        {
            List<ChessMove> moves = new List<ChessMove>();

            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    ChessPiece p = board[x, y];
                    ChessLocation from = new ChessLocation(x, y);

                    if (p != ChessPiece.Empty && !isEnemy(board, color, from))
                    {
                        HashSet<ChessLocation> locs = getFilteredPieceMoves(board, color, from);
                        foreach (ChessLocation to in locs)
                        {
                            moves.Add(new ChessMove(from, to));
                        }
                    }
                }
            }
            return moves;
        }

        public static ChessFlag getFlag(ChessBoard board, ChessColor color)
        {           

            if (Movement.inCheck(board, color))
            {
                if (Movement.getMoves(board, color).Count == 0)
                {
                    return ChessFlag.Checkmate;
                }
                else
                {
                    return ChessFlag.Check;
                }
            }/*
            else if (Movement.getMoves(board, color).Count == 0)
            {
                return ChessFlag.Stalemate;
            }*/
            else
            {
                return ChessFlag.NoFlag;
            }
        }

        /// <summary>
        /// Get a list of a players moves resulting in a capture
        /// </summary>
        /// <param name="board">The current board state</param>
        /// <param name="color">The color of pieces to get moves for</param>
        /// <returns>A list of the color's capture moves</returns>
        public static List<ChessMove> getCaptureMoves(ChessBoard board, ChessColor color)
        {
            List<ChessMove> moves = new List<ChessMove>();
            
            foreach(ChessMove move in getMoves(board, color))
            {
                
                if (isEnemy(board, color, move.To))               
                    moves.Add(move);
            }

            return moves;
        }

        #endregion

        #region Move validation functions
        public static bool isValidMove(ChessBoard board, ChessMove move, ChessColor color)
        {
            bool rv = true;

            //Make sure a move actually happened
            if (move.To == move.From || isEnemy(board, color, move.From) || !getFilteredPieceMoves(board, color, move.From).Contains(move.To))
            {
                rv = false;
            }

            return rv;
        }
        #endregion

        #region Move helper functions
        /**
         * Checks if the target spot contains an enemy piece, doesn't do range checking on target to see if it is on the board
         */
        private static bool isEnemy(ChessBoard board, ChessColor color, ChessLocation position)
        {
            ChessPiece p = board[position];

            //Check if there is a piece of the opposite color to attack
            return (color == ChessColor.White && p < ChessPiece.Empty) ||
                    (color == ChessColor.Black && p > ChessPiece.Empty);

        }

        private static HashSet<ChessLocation> filterOutCheck(ChessBoard board, ChessLocation from, HashSet<ChessLocation> locs, ChessColor color)
        {
            HashSet<ChessLocation> moves = new HashSet<ChessLocation>();
            foreach (ChessLocation to in locs)
            {
                if (!inCheck(applyMove(board, new ChessMove(from, to)), color)  && 
                    board[to]!=ChessPiece.BlackKing && board[to]!=ChessPiece.WhiteKing
                    )
                    moves.Add(to);
            }

            return moves;
        }

        private static ChessLocation findPiece(ChessBoard board, ChessPiece piece)
        {
            for (int y = 0; y < ChessBoard.NumberOfRows; y++)
            {
                for (int x = 0; x < ChessBoard.NumberOfColumns; x++)
                {
                    if (board[x, y] == piece)
                        return new ChessLocation(x, y);
                }
            }
            return new ChessLocation(-1, -1);
        }

        /// <summary>
        /// Generate a new board from a piece move
        /// </summary>
        /// <param name="board">The starting board state</param>
        /// <param name="move">The move to apply</param>
        /// <returns>A new board with the move applied</returns>
        public static ChessBoard applyMove(ChessBoard board, ChessMove move)
        {
            ChessBoard afterMove = board.Clone();
            afterMove.MakeMove(move);
            return afterMove;
        }

        /// <summary>
        /// Test if a player's king is in check
        /// </summary>
        /// <param name="board">The board state to test</param>
        /// <param name="kingColor">The player's color</param>
        /// <returns>True if the player's king is in check</returns>
        public static bool inCheck(ChessBoard board, ChessColor kingColor)
        {
            bool rv = false;
            
            //find the king location
            ChessLocation kingPosition = (kingColor == ChessColor.White ? findPiece(board, ChessPiece.WhiteKing) : findPiece(board, ChessPiece.BlackKing));
            ChessColor enemyColor = (kingColor == ChessColor.White ? ChessColor.Black : ChessColor.White);

            //get every move a queen and knight could make from this position
            //at each move, check if there is an enemy and if they are able to attack the king.
            HashSet<ChessLocation> attackers = getQueenMoves(board, kingColor, kingPosition);
            attackers.UnionWith(getKnightMoves(board, kingColor, kingPosition));
            foreach (ChessLocation attackerPosition in attackers)
            {
                //***test the attackers moves without filtering out check***
                if(isEnemy(board, kingColor, attackerPosition) && getPieceMoves(board, enemyColor, attackerPosition).Contains(kingPosition))
                {
                    rv = true;
                    break;
                }
            }

            return rv;
        }
        #endregion
    }
}